package com.kexilo.core.common.aspect;

import com.kexilo.core.common.annotation.Idempotent;
import com.kexilo.core.common.exception.ServiceException;
import com.kexilo.core.common.utils.IpUtils;
import com.kexilo.core.common.utils.RedisUtils;
import com.kexilo.core.common.utils.SecurityUtils;
import com.kexilo.core.common.utils.ServletUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.concurrent.TimeUnit;

/**
 * 幂等性切面处理器
 * 
 * @author Kexilo
 */
@Aspect
@Component
public class IdempotentAspect {
    
    private static final Logger log = LoggerFactory.getLogger(IdempotentAspect.class);
    
    @Autowired
    private RedisUtils redisUtils;
    
    private final ExpressionParser parser = new SpelExpressionParser();
    
    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
        String idempotentKey = generateIdempotentKey(joinPoint, idempotent);
        
        // 检查是否已经存在相同的请求
        if (redisUtils.hasKey(idempotentKey)) {
            log.warn("幂等性检查失败，重复请求: key={}", idempotentKey);
            throw new ServiceException(idempotent.message());
        }
        
        // 设置幂等性标识
        long timeoutInSeconds = idempotent.timeUnit().toSeconds(idempotent.timeout());
        redisUtils.set(idempotentKey, "1", timeoutInSeconds);
        
        try {
            // 执行方法
            Object result = joinPoint.proceed();
            
            // 如果设置了操作完成后删除key，则删除
            if (idempotent.delKey()) {
                redisUtils.del(idempotentKey);
            }
            
            log.debug("幂等性检查通过，操作完成: key={}", idempotentKey);
            return result;
        } catch (Exception e) {
            // 如果执行失败，删除幂等性标识，允许重试
            redisUtils.del(idempotentKey);
            log.warn("方法执行失败，删除幂等性标识: key={}", idempotentKey);
            throw e;
        }
    }
    
    /**
     * 生成幂等性key
     */
    private String generateIdempotentKey(ProceedingJoinPoint joinPoint, Idempotent idempotent) {
        StringBuilder keyBuilder = new StringBuilder(idempotent.value());
        
        String customKey = idempotent.key();
        if (StringUtils.hasText(customKey)) {
            // 使用SpEL表达式解析自定义key
            String parsedKey = parseSpelExpression(joinPoint, customKey);
            keyBuilder.append(parsedKey);
        } else {
            // 根据策略生成key
            switch (idempotent.strategy()) {
                case USER_BASED:
                    keyBuilder.append(getUserBasedKey());
                    break;
                case IP_BASED:
                    keyBuilder.append(getIpBasedKey());
                    break;
                case REQUEST_PARAMS:
                    keyBuilder.append(getRequestParamsKey(joinPoint));
                    break;
                case CUSTOM_KEY:
                    // 自定义key已在上面处理
                    break;
                default:
                    keyBuilder.append(getRequestParamsKey(joinPoint));
            }
        }
        
        return keyBuilder.toString();
    }
    
    /**
     * 解析SpEL表达式
     */
    private String parseSpelExpression(ProceedingJoinPoint joinPoint, String expression) {
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Object[] args = joinPoint.getArgs();
            Parameter[] parameters = method.getParameters();
            
            EvaluationContext context = new StandardEvaluationContext();
            
            // 设置方法参数
            for (int i = 0; i < parameters.length; i++) {
                context.setVariable(parameters[i].getName(), args[i]);
            }
            
            // 设置常用变量
            try {
                context.setVariable("userId", SecurityUtils.getUserId());
                context.setVariable("username", SecurityUtils.getUsername());
            } catch (Exception e) {
                log.debug("获取用户信息失败，使用默认值");
                context.setVariable("userId", "anonymous");
                context.setVariable("username", "anonymous");
            }
            
            context.setVariable("ip", IpUtils.getIpAddr(ServletUtils.getRequest()));
            context.setVariable("methodName", method.getName());
            context.setVariable("className", method.getDeclaringClass().getSimpleName());
            
            Expression exp = parser.parseExpression(expression);
            Object value = exp.getValue(context);
            return value != null ? value.toString() : "";
        } catch (Exception e) {
            log.error("解析SpEL表达式失败: {}", expression, e);
            return expression;
        }
    }
    
    /**
     * 获取基于用户的key
     */
    private String getUserBasedKey() {
        try {
            return "user:" + SecurityUtils.getUserId();
        } catch (Exception e) {
            return "user:anonymous:" + IpUtils.getIpAddr(ServletUtils.getRequest());
        }
    }
    
    /**
     * 获取基于IP的key
     */
    private String getIpBasedKey() {
        return "ip:" + IpUtils.getIpAddr(ServletUtils.getRequest());
    }
    
    /**
     * 获取基于请求参数的key
     */
    private String getRequestParamsKey(ProceedingJoinPoint joinPoint) {
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Object[] args = joinPoint.getArgs();
            
            StringBuilder sb = new StringBuilder();
            sb.append("method:").append(method.getDeclaringClass().getSimpleName())
              .append(".").append(method.getName());
            
            // 添加参数hash
            if (args != null && args.length > 0) {
                sb.append(":params:");
                for (Object arg : args) {
                    if (arg != null) {
                        sb.append(arg.toString()).append(",");
                    }
                }
            }
            
            // 对长key进行hash处理
            String fullKey = sb.toString();
            if (fullKey.length() > 200) {
                return "hash:" + md5(fullKey);
            }
            
            return fullKey;
        } catch (Exception e) {
            log.error("生成请求参数key失败", e);
            return "default:params";
        }
    }
    
    /**
     * MD5加密
     */
    private String md5(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (Exception e) {
            return String.valueOf(input.hashCode());
        }
    }
}
